/**
 * 
 */
package org.codesketch.birch.command;

import java.util.Collection;
import java.util.List;

import org.codesketch.birch.common.BirchRequest;
import org.codesketch.birch.common.BirchResponse;
import org.codesketch.birch.exception.BirchCommandException;


import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.model.CreateTagsRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.DescribeTagsResult;
import com.amazonaws.services.ec2.model.InstanceStateChange;
import com.amazonaws.services.ec2.model.InstanceStateName;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ec2.model.RunInstancesRequest;
import com.amazonaws.services.ec2.model.RunInstancesResult;
import com.amazonaws.services.ec2.model.StartInstancesRequest;
import com.amazonaws.services.ec2.model.StartInstancesResult;
import com.amazonaws.services.ec2.model.StopInstancesRequest;
import com.amazonaws.services.ec2.model.StopInstancesResult;
import com.amazonaws.services.ec2.model.TerminateInstancesRequest;
import com.amazonaws.services.ec2.model.TerminateInstancesResult;

/**
 * Exposes the methods to execute a command against AWS.
 * @author Quirino Brizi
 *
 */
final class Command {
	
	/**
	 * Send a run instance request to AWS. After the request sending the methods waits for the complete transition 
	 * for a maximum of 5 minutes, after returns setting the {@link BirchResponse}'s transition flag to false. 
	 * That can or cannot be ignored by the user that may be is aware of a long transition time, in any case, along 
	 * with the transition flag the {@link BirchResponse} is caring the AWS {@link Reservation} object so the user 
	 * is free to handle each state taking the appropriate action. 
	 * @param birch the {@link Birch} client
	 * @param request the crowd run request 
	 * @return the crowd response that contains the transition status and the AWS detailed response.
	 */
	static BirchResponse<Reservation> run(final Birch birch, final BirchRequest request) {
		
		RunInstancesRequest req = new RunInstancesRequest();
		map(request, req);
		
		final AmazonEC2 ec2 = birch.client();
		final RunInstancesResult res = ec2.runInstances(req);
		
		final Reservation reservation = res.getReservation();
		final Collection<String> instancesId = CommandUtils.getInstancesId(reservation);
		boolean tsValid = CommandUtils.waitForTransition(birch, instancesId, InstanceStateName.Running);
		
		return prepare(birch, tsValid, instancesId);
	}
	/**
	 * Send a terminate request to AWS. After the request sending the methods waits for the complete transition 
	 * for a maximum of 5 minutes, after returns setting the {@link BirchResponse}'s transition flag to false. 
	 * That can or cannot be ignored by the user that may be is aware of a long transition time, in any case, along 
	 * with the transition flag the {@link BirchResponse} is caring the list of AWS {@link InstanceStateChange} object 
	 * so the user is free to handle each state taking the appropriate action.
	 * @param birch the {@link Birch} client
	 * @param request the crowd terminate request 
	 * @return the crowd response that contains the transition status and the AWS detailed response.
	 */
	static BirchResponse<Reservation> terminate(final Birch birch, final BirchRequest request) {
		TerminateInstancesRequest req = new TerminateInstancesRequest();
		map(request, req);
		
		final AmazonEC2 ec2 = birch.client();
		TerminateInstancesResult res = ec2.terminateInstances(req);
		
		List<InstanceStateChange> instancesState = res.getTerminatingInstances();
		final Collection<String> instancesId = CommandUtils.getInstancesId(instancesState);
		boolean tsValid = CommandUtils.waitForTransition(birch, instancesId, InstanceStateName.Terminated);
		
		return prepare(birch, tsValid, instancesId);
	}
	/**
	 * Send a new tag request to AWS. Adds or overrides tags for the specified resources. Each instance 
	 * can have a maximum of 10 tags.The methods returns immediately after the AWS's request sending, 
	 * @param crowd the {@link Birch} client
	 * @param request the crowd tag request
	 * @return the crowd response that contains the transition flag and the AWS detailed response.
	 */
	static BirchResponse<DescribeTagsResult> tag(final Birch crowd, final BirchRequest request) {
		final CreateTagsRequest req = new CreateTagsRequest();
		map(request, req);
		
		final AmazonEC2 ec2 = crowd.client();
		ec2.createTags(req);
		
		DescribeTagsResult describeResult = ec2.describeTags();
		return new BirchResponse<DescribeTagsResult>(true, describeResult);
	}
	/**
	 * Convenient method to send a start request to AWS.
	 * @param birch the {@link Birch} client
	 * @param request the crowd start request
	 * @return the crowd response containing the transition state and AWS detailed response.
	 * @throws BirchCommandException if the {@link BirchRequest} cannot be mapped to a valid {@link AmazonWebServiceRequest}
	 */
	static BirchResponse<Reservation> start(final Birch birch, final BirchRequest request) {
		final StartInstancesRequest req = new StartInstancesRequest();
		map(request, req);
		
		final AmazonEC2 ec2 = birch.client();
		final StartInstancesResult res = ec2.startInstances(req);
		
		final List<InstanceStateChange> instancesState = res.getStartingInstances();
		final Collection<String> instancesId = CommandUtils.getInstancesId(instancesState);
		boolean tsValid = CommandUtils.waitForTransition(birch, instancesId, InstanceStateName.Running);
		
		return prepare(birch, tsValid, instancesId);
	}
	/**
	 * Convenient method to send a stop request to AWS.
	 * @param birch the {@link Birch} client
	 * @param request the crowd start request
	 * @return the crowd response containing the transition state and AWS detailed response.
	 * @throws BirchCommandException if the {@link BirchRequest} cannot be mapped to a valid {@link AmazonWebServiceRequest}
	 */
	static BirchResponse<Reservation> stop(final Birch birch, final BirchRequest request) {
		final StopInstancesRequest req = new StopInstancesRequest();
		map(request, req);
		
		final AmazonEC2 ec2 = birch.client();
		final StopInstancesResult res = ec2.stopInstances(req);
		
		final List<InstanceStateChange> instancesState = res.getStoppingInstances();
		final Collection<String> instancesId = CommandUtils.getInstancesId(instancesState);
		boolean tsValid = CommandUtils.waitForTransition(birch, instancesId, InstanceStateName.Stopped);
		
		return prepare(birch, tsValid, instancesId);
	}
	/**
	 * Sends a describe instance request to AWS EC2.
	 * @param birch the {@link Birch} client
	 * @param request the describe request definition as a {@link BirchRequest}
	 * @return a {@link BirchResponse} containing the instances description from AWS EC2.
	 */
	static BirchResponse<List<Reservation>> describe(final Birch birch, final BirchRequest request) {
		final DescribeInstancesRequest req = new DescribeInstancesRequest();
		map(request, req);
		
		final AmazonEC2 ec2 = birch.client();
		final DescribeInstancesResult res = ec2.describeInstances(req);
		return new BirchResponse<List<Reservation>>(true, res.getReservations());
	}
	
	// -- HELPER
	
	/**
	 * Maps the {@link BirchRequest} to a {@link AmazonWebServiceRequest}
	 * @param crowdRequest the {@link BirchRequest} object
	 * @param awsRequest an object that extends {@link AmazonWebServiceRequest}
	 * @throws BirchCommandException if the {@link BirchRequest} cannot be mapped to a valid {@link AmazonWebServiceRequest}
	 */
	static void map(
			final BirchRequest crowdRequest, final AmazonWebServiceRequest awsRequest) {
		try {
			CommandUtils.map(crowdRequest, awsRequest);
		} catch (Exception e) {
			throw new BirchCommandException(String.format("Unable to map BirchRequest to AwsRequest: %s", e.getMessage()), e);
		}
	}
	/**
	 * Prepare the {@link BirchResponse}
	 * @param birch the client
	 * @param tsValid the transition status flag
	 * @param instancesId the list of instances to have to describe
	 * @return a {@link BirchResponse}
	 */
	static BirchResponse<Reservation> prepare(final Birch birch, final Boolean tsValid, Collection<String> instancesId) {
		BirchResponse<Reservation> response;
		if(tsValid) {
			// valid transition update get the current instance(s) status
			final BirchRequest describeReq = new BirchRequest();
			describeReq.put(RequestField.DESCRIBE_INSTANCES_ID, instancesId);
			response = new BirchResponse<Reservation>(tsValid, describe(birch, describeReq).getResponse().get(0));
 		} else {
			response = new BirchResponse<Reservation>(tsValid, new Reservation());
		}
		return response;
	}
}
